﻿using Com.Lancetinc.Evs.Cameras;
using Com.Lancetinc.Evs.Events;
using Com.Lancetinc.Evs.Helpers;
using Com.Lancetinc.Evs.Models;
using Com.Lancetinc.Evs.Properties;
using Com.Lancetinc.Evs.Services;
using Com.Lancetinc.Evs.Windows;
using NLog;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using Telerik.Windows.Controls;
using HorizontalAlignment = System.Windows.HorizontalAlignment;
using Size = System.Windows.Size;

namespace Com.Lancetinc.Evs.Controls
{
    /// <summary>
    /// 录像中
    /// </summary>
    public partial class VideoCaptureUserControl : IVideoUserControl
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        private string capturedImageSaveFileName;
        private Size mExpectedMediaSize = Size.Empty;

        private Size mRawVideoSize = new(400, 400);
        private string mRecordingFilePath; //最终录像文件名1.mp4
        private string mCurrentRecordingFilePath; // 当前正在录像的录像文件名(存在多次录像，名称为1.mp4,2.mp4,3.mp4等)
        private string mVideoFormatName;
        private string mVideoInputName;
        private int _currentVideoNo = 1;

        public VideoCaptureUserControl()
        {
            VideoRotationAngle = 0;
            InitializeComponent();

            _videoMagnification = Settings.Default.VideoScaleRatio;
        }

        private double _videoMagnification = 1;

        public double VideoMagnification
        {
            get => _videoMagnification;
            set
            {
                if (_videoMagnification == value)
                    return;

                if (value == 0)
                    value = 2;
                mExpectedMediaSize = new Size(mRawVideoSize.Width * value, mRawVideoSize.Height * value);
                DoSetVideoContainerSize(mExpectedMediaSize);
                _videoMagnification = value;
            }
        }

        public VideoStateEnum VideoState { get; private set; } = VideoStateEnum.Stop;

        public event EventHandler<VideoStateChangeEventArgs> VideoStateChanged;

        public void CaptureImage(string saveFileName)
        {
            capturedImageSaveFileName = saveFileName;
            radWebCam.TakeSnapshot();
        }

        public void Pause()
        {
            throw new NotSupportedException();
        }

        public async Task<bool> Play()
        {
            _currentVideoNo = 1;
            var instManager = ActiveApp.Instance.InstManager;

            try
            {
                this.VideoRotationAngle = instManager.Instrument.GetVideoRotationAngle();

                CreateWebCam();
                await WaitRadWebCamLoad();

                if (!InitializeWebCam())
                    return false;

                var flipConfig = VideoStartRecordHelper.GetVideoFlipConfig();
                radWebCam.RecordingFilePath = mRecordingFilePath;
                mCurrentRecordingFilePath = mRecordingFilePath;
                // 注意：镜像会影响显示和截图，但是不影响保存的视频
                radWebCam.FlipHorizontally = flipConfig.FlipHorizontally;
                radWebCam.FlipVertically = flipConfig.FlipVertically;

                radWebCam.Start();
                radWebCam.StartRecording();

                OnVideoStateChanged(VideoStateEnum.Playing);
                _recordingChecker.Start();
                return true;
            }
            catch (Exception exc)
            {
                Logger.Error(exc);
            }

            return false;
        }

        private RadWebCam radWebCam = null;

        /// <summary>
        /// 创建摄像头控件
        /// </summary>
        private void CreateWebCam()
        {
            if (radWebCam != null)
            {
                radWebCam.CameraError -= RadWebCam_CameraError;
                radWebCam.SnapshotTaken -= RadWebCam_SnapshotTaken;
                radWebCam.RecordingStarted -= RadWebCam_RecordingStarted;
                radWebCam.RecordingEnded -= RadWebCam_RecordingEnded;

                mainGrid.Children.Remove(radWebCam);
                radWebCam.Dispose();
            }

            _lastCameraError = null;
            radWebCam = new RadWebCam();
            radWebCam.AutoStart = false;
            radWebCam.FlipHorizontally = this.FlipHorizontally;
            radWebCam.FlipVertically = this.FlipVertically;
            radWebCam.PreviewSnapshots = false;
            radWebCam.ToolbarPanelVisibility = Visibility.Collapsed;
            if (this.VideoRotationAngle != 0)
            {
                radWebCam.LayoutTransform = new RotateTransform(this.VideoRotationAngle);
            }

            radWebCam.BorderThickness = new Thickness(0);
            radWebCam.MinWidth = 160;
            radWebCam.MinHeight = 160;
            radWebCam.Background = Background;
            radWebCam.CameraError += RadWebCam_CameraError;
            radWebCam.SnapshotTaken += RadWebCam_SnapshotTaken;
            radWebCam.RecordingStarted += RadWebCam_RecordingStarted;
            radWebCam.RecordingEnded += RadWebCam_RecordingEnded;
            radWebCam.VerticalAlignment = VerticalAlignment.Stretch;
            radWebCam.HorizontalAlignment = HorizontalAlignment.Stretch;
            mainGrid.Children.Add(radWebCam);
        }

        private Task WaitRadWebCamLoad()
        {
            if (radWebCam.IsLoaded)
                return Task.CompletedTask;
            return Task.Factory.StartNew(() =>
            {
                int times = 30 * 5; // 最多等待5S
                bool loaded = false;
                while (!loaded && times-- > 0)
                {
                    WpfHelper.ExecSafe(() => { loaded = radWebCam.IsLoaded; });
                    if (loaded)
                        break;
                    Thread.Sleep(33);
                }

                if (!loaded)
                {
                    Logger.Warn("wait radWebCam load failed");
                }
            });
        }

        private bool InitializeWebCam()
        {
            // https://docs.telerik.com/devtools/wpf/controls/radwebcam/overview
            var videoDevices = RadWebCam.GetVideoCaptureDevices();
            var videoDevice = videoDevices.FirstOrDefault(x => x.FriendlyName == mVideoInputName);
            if (videoDevice == null)
                return false;

            var info = new VideoDeviceInfo
            {
                VideoFormat = mVideoFormatName,
                VideoWidth = (int)mRawVideoSize.Width,
                VideoHeight = (int)mRawVideoSize.Height
            };
            var videoFormat = RadWebCamHelper.GetVideoFormat(videoDevice, info);
            radWebCam.Initialize(videoDevice, videoFormat, null);
            return true;
        }

        /// <summary>
        /// 最后一次异常
        /// </summary>
        private KeyValuePair<long, ErrorInfo>? _lastCameraError = null;
        private void RadWebCam_CameraError(object sender, CameraErrorEventArgs e)
        {
            _lastCameraError = new KeyValuePair<long, ErrorInfo>(Environment.TickCount, e.Error);
            Logger.Warn($"{e.Error.ErrorState},{e.Error.Message}");
        }

        public async void Stop(RequireStopReason reason)
        {
            if (reason == RequireStopReason.UserStop)
            {
                StopWebCam(true, true);
                return;
            }

            await Task.Delay(TimeSpan.FromSeconds(1));
            if (IsRealRecording())
                return; // 虽然串口断了，但是其实还在录像中

            await TryWaitCameraAndReRecord(reason.ToString());
        }

        /// <summary>
        /// 有时候虽然能找到设备，但是处于不可以状态
        /// 因此开始之后检查一次是否真的正在录像，如果不是，则重试一次
        /// </summary>
        private async Task CheckRecordAndWriteBalanceAfterPlay()
        {
            if (VideoState != VideoStateEnum.Playing)
                return;

            if (_lastCameraError != null)
            {
                await TryWaitCameraAndReRecord("_lastCameraError != null");
                return;
            }

            if (!IsRealRecording())
            {
                await TryWaitCameraAndReRecord("IsRealRecording == false");
                return;
            }
            WriteBalance();
        }

        private void WriteBalance()
        {
            try
            {
                var ok = RadWebCamHelper.LoadWhiteBalance(mVideoInputName);
                Logger.Debug("LoadWhiteBalance:" + ok);
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
            }
        }

        private string GetNextVideoPath()
        {
            var dir = Path.GetDirectoryName(mRecordingFilePath);
            var ext = Path.GetExtension(mRecordingFilePath);
            _currentVideoNo++;
            return Path.Combine(dir, _currentVideoNo + ext);
        }

        private bool _waitingCameraReconnect = false;
        /// <summary>
        /// 尝试查找摄像头，如果找到则重新录像
        /// </summary>
        /// <returns></returns>
        private async Task<bool> TryWaitCameraAndReRecord(string reason)
        {
            if (_waitingCameraReconnect)
                return false; // 已经在等待了

            _waitingCameraReconnect = true;
            try
            {
                Logger.Info("TryWaitCameraAndReRecord.start." + reason);
                var disp = await WaitBoxHelper.WaitBox.ShowAsync("", I18nHelper.Default.GetMessage("FindingVideoDevice"),
                    true, false);

                var cts = new CancellationTokenSource(TimeSpan.FromSeconds(12));
                disp.OnDisposed += (_, _) => { cts.Cancel(); };
                bool cameraConnected = await TryWaitCameraReconnect(cts.Token);
                disp.Dispose();
                if (!cameraConnected)
                {
                    StopWebCam(true, true);
                    return false; // 摄像头还是不可用
                }

                // 如果摄像有又有用了，则尝试关闭后重新打开
                ReRecord();
                return true;
            }
            finally
            {
                _waitingCameraReconnect = false;
            }
        }

        private Task<bool> TryWaitCameraReconnect(CancellationToken ct)
        {
            return Task.Factory.StartNew(() =>
            {
                var ins = ActiveApp.Instance.InstManager.Instrument;
                while (!ct.IsCancellationRequested)
                {
                    if (!(ins.InstrumentConnected && ins.EndoscopicConnected) ||
                        !RadWebCamHelper.IsCameraExists(mVideoInputName))
                    {
                        Thread.Sleep(500);
                        continue;
                    }

                    return true;
                }

                return false;
            }, ct);
        }

        private void ReRecord()
        {
            StopWebCam(false, false);
            InitializeWebCam();
            radWebCam.RecordingFilePath = GetNextVideoPath();
            mCurrentRecordingFilePath = radWebCam.RecordingFilePath;
            radWebCam.Start();
            radWebCam.StartRecording();
            _recordingChecker.UpdateFile(mCurrentRecordingFilePath);
            _recordingChecker.Start();
        }

        private RadWebCamRecordingChecker _recordingChecker;
        /// <summary>
        /// 判断摄像头控件是否正在录像
        /// </summary>
        /// <returns></returns>
        private bool IsRealRecording()
        {
            if (_recordingChecker.IsRecording)
                return true;
            return _recordingChecker.CheckNow();
        }

        private void StopWebCam(bool notify, bool shutdown)
        {
            radWebCam.StopRecording();
            radWebCam.Stop();
            _recordingChecker.Stop();

            if (shutdown)
            {
                radWebCam.ShutDown();
                if (_currentVideoNo > 1)
                {
                    TryConnectVideos(_currentVideoNo);
                }
            }

            if (notify) OnVideoStateChanged(VideoStateEnum.Stop);

            // 释放设备
            HCameraLib.hFreeVideoDevice();
            GC.Collect();
        }

        private void TryConnectVideos(int videoCount)
        {
            if (videoCount < 2) return;

            var rootDir = Path.GetDirectoryName(mRecordingFilePath);
            var ext = Path.GetExtension(mRecordingFilePath);
            Task.Factory.StartNew(() =>
            {
                Thread.Sleep(500); // 稍微等一会
                var files = new List<string>();
                for (int i = 1; i <= videoCount; i++)
                {
                    var videoFile = new FileInfo(Path.Combine(rootDir, i + ext));
                    if (!videoFile.Exists || videoFile.Length < 100)
                        continue; // 文件不存在，或者文件太小
                    files.Add(videoFile.FullName);
                }

                if (files.Count < 2) return;
                VideoHelper.Join(mRecordingFilePath, files.ToArray());
            });
        }

        private void RadWebCam_RecordingStarted(object sender, RecordingStartedEventArgs e)
        {
            Logger.Info($"RecordingStarted,No={_currentVideoNo},Rotation={VideoRotationAngle},FlipHor={radWebCam.FlipHorizontally},FlipVer={radWebCam.FlipVertically}");
            WpfHelper.ExecLater(() =>
            {
                if (_currentVideoNo == 1)
                {
                    CheckRecordAndWriteBalanceAfterPlay();
                }
                else
                {
                    WriteBalance();
                }
            }, 3000);
        }

        private void RadWebCam_RecordingEnded(object sender, RoutedEventArgs e)
        {
            Logger.Info($"RadWebCam_RecordingEnded,No={_currentVideoNo}");
        }

        private void RadWebCam_SnapshotTaken(object sender, SnapshotTakenEventArgs e)
        {
            MediaElementBaseHelper.CaptureImage(e.Snapshot, capturedImageSaveFileName, (int)mRawVideoSize.Width,
                VideoRotationAngle);
        }

        public void SetCaptureParams(string videoInputName, ref short videoWidth, ref short videoHeight,
            string videoFormatName, string outputFileName)
        {
            var videoDevices = RadWebCam.GetVideoCaptureDevices();
            var videoDevice = videoDevices.FirstOrDefault(x => x.FriendlyName == videoInputName);
            var info = new VideoDeviceInfo
            {
                VideoFormat = videoFormatName,
                VideoWidth = videoWidth,
                VideoHeight = videoHeight
            };
            mVideoFormatName = videoFormatName;
            var videoFormat = RadWebCamHelper.GetVideoFormat(videoDevice, info);
            mRecordingFilePath = outputFileName;
            mVideoInputName = videoInputName;

            videoWidth = (short)videoFormat.FrameSizeWidth;
            videoHeight = (short)videoFormat.FrameSizeHeight;

            mRawVideoSize = new Size(videoWidth, videoHeight);
            mExpectedMediaSize = new Size(videoWidth * _videoMagnification, videoHeight * _videoMagnification);

            DoSetVideoContainerSize(mExpectedMediaSize);
            _recordingChecker = new RadWebCamRecordingChecker(mRecordingFilePath);
        }

        private void DoSetVideoContainerSize(Size expectedSize)
        {
            var maxWidth = ActualWidth;
            var maxHeight = ActualHeight;

            var actualWidth = Math.Min(maxWidth, expectedSize.Width);
            var actualHeight = Math.Min(maxHeight, expectedSize.Height);

            var actualWH = actualWidth / actualHeight;
            var expectedWH = expectedSize.Width / expectedSize.Height;

            if (actualWH > expectedWH)
            {
                actualWidth = actualHeight * expectedWH;
            }
            else if (actualWH < expectedWH)
            {
                actualHeight = actualWidth / expectedWH;
            }

            mainGrid.Width = actualWidth;
            mainGrid.Height = actualHeight;
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged(sizeInfo);
            if (VideoState != VideoStateEnum.Playing)
                return;

            if (mExpectedMediaSize != Size.Empty)
            {
                DoSetVideoContainerSize(mExpectedMediaSize);
            }
        }

        private void OnVideoStateChanged(VideoStateEnum videoState)
        {
            VideoState = videoState;
            VideoStateChanged?.Invoke(this, new VideoStateChangeEventArgs(videoState));
        }

        private void ThisCtrl_Unloaded(object sender, RoutedEventArgs e)
        {
            _recordingChecker?.Dispose();

            if (radWebCam?.IsRecording == true)
                StopWebCam(true, true);
        }

        #region VideoRotationAngle

        public static readonly DependencyProperty VideoRotationAngleProperty =
            DependencyProperty.Register("VideoRotationAngle", typeof(double), typeof(VideoCaptureUserControl));

        public double VideoRotationAngle
        {
            get => (double)GetValue(VideoRotationAngleProperty);
            set => SetValue(VideoRotationAngleProperty, value);
        }

        #endregion

        #region FlipHorizontally

        public static readonly DependencyProperty FlipHorizontallyProperty =
            DependencyProperty.Register("FlipHorizontally", typeof(bool), typeof(VideoCaptureUserControl));

        public bool FlipHorizontally
        {
            get => (bool)GetValue(FlipHorizontallyProperty);
            set => SetValue(FlipHorizontallyProperty, value);
        }

        #endregion

        #region FlipVertically

        public static readonly DependencyProperty FlipVerticallyProperty =
            DependencyProperty.Register("FlipVertically", typeof(bool), typeof(VideoCaptureUserControl));

        public bool FlipVertically
        {
            get => (bool)GetValue(FlipVerticallyProperty);
            set => SetValue(FlipVerticallyProperty, value);
        }

        #endregion
    }
}